package generatorImplementations.misc;

import generator.Generator;
import generator.GeneratorType;
import generator.properties.AnimationPropertiesContainer;
import java.awt.Color;
import java.awt.Font;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;

import com.sun.org.apache.xerces.internal.impl.xs.models.XSAllCM;

import algoanim.animalscript.AnimalScript;
import algoanim.primitives.Rect;
import algoanim.primitives.SourceCode;
import algoanim.primitives.StringMatrix;
import algoanim.primitives.Text;
import algoanim.primitives.generators.Language;
import algoanim.properties.AnimationPropertiesKeys;
import algoanim.properties.MatrixProperties;
import algoanim.properties.RectProperties;
import algoanim.properties.SourceCodeProperties;
import algoanim.properties.TextProperties;
import algoanim.util.Coordinates;
import algoanim.util.Offset;
import algoanim.util.TicksTiming;
import algoanim.util.Timing;

public class ErweiterterEuklid implements Generator {

	static class SimpleFileWriter {
		public static void writeFile(File selSort, String data) {
			try {
				FileWriter fw = new FileWriter(selSort);
				BufferedWriter bw = new BufferedWriter(fw);
				bw.write(data);
				bw.close();
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private String text = "Mit dem erweiterten euklidischen Algorithmus berechnet man ein "
			+ "x und ein y, so dass die Gleichung ax+by = gcd(a, b) gel&ouml;st werden kann. Damit kann "
			+ "man den gr&ouml;&szlig;ten gemeinsamen Teiler finden. Verwendet "
			+ "wird er zum Beispiel bei der Schl&uuml;sselberechnung des RSA-Verschl&uuml;sselungs-"
			+ "Verfahrens. Eingabedaten sind a und b.";

	private String pseudoCodeBuchmann = " xeulid(int a, int b, int gcd, int x, int y) {"
			+ " <br>"
			+ " begin<br>"
			+ "   int q, r, xx, yy, sign<br>"
			+ "   int xs[2], ys[2]<br>"
			+ "   xs[0] = 1 xs[1] = 0<br>"
			+ "   ys[0} = 0 ys[1] = 1<br>"
			+ "   sign = 1<br>"
			+ "   while (b != 0)<br>"
			+ "     r = a%b<br>"
			+ "     q = a/b<br>"
			+ "     a = b<br>"
			+ "     b = r<br>"
			+ "     xx = xs[1]<br>"
			+ "     yy = ys[1]<br>"
			+ "     xs[1] = q*xs[1] + xs[0]<br>"
			+ "     ys[1] = q*ys[1] + ys[0]<br>"
			+ "     xs[0] = xx<br>"
			+ "     ys[0] = yy<br>"
			+ "     sign = -sign<br>"
			+ "   end while<br>"
			+ "   x = sign*xs[0]<br>"
			+ "   y = -sign*ys[0]<br>" + "   gcd = a<br>" + " end";

	/*
	 * Felder fuer Darstellung
	 */
	private final String HEADER_TITLE = "Euklidischer Algorithmus";
	private final String AUTHOR = "Tuba Goezel und Johannes Born";
	private final String PSEUDOCODE_TITLE = "Pseudocode aus \\\"Einfuehrung "
			+ "in die Kryptographie\\\" von Johannes Buchmann";
	private final String P_CODE_TITLE = "Pseudocode aus \"Einfuehrung "
			+ "in die Kryptographie\" von Johannes Buchmann";

	private Timing defaultTiming;
	private final String A_INIT = "a = ";
	private final String B_INIT = "b = ";

	private final String R = "r = a mod b = ";
	private final String Q = "q = a/b = ";
	private final String A = "a = b = ";
	private final String B = "b = r = ";

	private final String GCD = "gcd(a,b) = a = ";
	private final String X = "x = sign*x0 = ";
	private final String Y = "y = -sign*y0 = ";
	private final String SIGN1 = "sign = ";
	private final String SIGN2 = "sign = -sign = ";
	private final String XTMP = "xtmp = x1 = ";
	private final String YTMP = "ytmp = y1 = ";

	private Language lang;
	private Text header;
	private Rect headerBackground;
	private SourceCode code;
	private StringMatrix table;

	private List<String> rk = new ArrayList<String>();
	private List<String> qk = new ArrayList<String>();
	private List<String> xk = new ArrayList<String>();
	private List<String> yk = new ArrayList<String>();

	private Text textA;
	private Text textB;
	private Text textR;
	private Text textQ;
	private Text textX;
	private Text textY;
	private Text textGCD;
	private Text textSIGN;
//	private Text textXTMP;
//	private Text textYTMP;

	public ErweiterterEuklid() {
		header = generateHeader();
		headerBackground = generateHeaderBackground();
	}

	public void init() {
		lang = new AnimalScript(HEADER_TITLE, AUTHOR, 640, 480);
		lang.setStepMode(true);
		SourceCodeProperties props = new SourceCodeProperties();
		props.set(AnimationPropertiesKeys.HIGHLIGHTCOLOR_PROPERTY, Color.BLUE);
		props.set(AnimationPropertiesKeys.BOLD_PROPERTY, true);
		// sourceCode = lang.newSourceCode(new Coordinates(20, 10), "sumupCode",
		// null, props);
	}

	private void generateText() {
		textA = lang.newText(
				new Offset(200, 0, code, AnimalScript.DIRECTION_NE), A_INIT,
				"A", null);
		textB = lang.newText(
				new Offset(0, 10, textA, AnimalScript.DIRECTION_SW), B_INIT,
				"B", null);
		textR = lang.newText(
				new Offset(0, 5, textB, AnimalScript.DIRECTION_SW), R, "A",
				null);
		textQ = lang.newText(
				new Offset(0, 5, textR, AnimalScript.DIRECTION_SW), Q, "A",
				null);
		textX = lang.newText(
				new Offset(0, 5, textQ, AnimalScript.DIRECTION_SW), X, "A",
				null);
		textY = lang.newText(
				new Offset(0, 5, textX, AnimalScript.DIRECTION_SW), Y, "A",
				null);
		textGCD = lang.newText(new Offset(0, 5, textY,
				AnimalScript.DIRECTION_SW), GCD, "A", null);
		textSIGN = lang.newText(new Offset(0, 5, textGCD,
				AnimalScript.DIRECTION_SW), SIGN1, "A", null);
//		textXTMP = lang.newText(new Offset(0, 5, textSIGN,
//				AnimalScript.DIRECTION_SW), XTMP, "A", null);
//		textYTMP = lang.newText(new Offset(0, 5, textXTMP,
//				AnimalScript.DIRECTION_SW), YTMP, "A", null);

		textA.hide();
		textB.hide();
		textR.hide();
		textQ.hide();
		textX.hide();
		textY.hide();
		textGCD.hide();
		textSIGN.hide();
//		textXTMP.hide();
//		textYTMP.hide();

	}

	/*
	 * Methoden fuer die Darstellung
	 */

	/**
	 * Generiert die Ueberschrift fuer die Darstellung des Erweiterten Euklid
	 * 
	 * @return Text header;
	 */
	private Text generateHeader() {
		TextProperties textProperties = new TextProperties();
		textProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		textProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		return lang.newText(new Coordinates(20, 30), HEADER_TITLE + " von "
				+ AUTHOR, "header", null, textProperties);
	}

	/**
	 * Generiert den Hintergrund fuer die Ueberschrift
	 * 
	 * @return headerBackground
	 */
	private Rect generateHeaderBackground() {
		RectProperties rectProperties = new RectProperties();
		rectProperties.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.GRAY);
		rectProperties.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		rectProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 2);
		return lang.newRect(new Offset(-5, -5, header,
				AnimalScript.DIRECTION_NW), new Offset(5, 5, header,
				AnimalScript.DIRECTION_SE), "HeaderBackground", null,
				rectProperties);
	}

	/**
	 * Erzeugt den Pseudocode des Erweiterten Euklid nach "Einfuehrung in die
	 * Kryptographie" nach Johannes Buchmann
	 * 
	 * @return pseudoCode
	 */
	private void generatePseudoCode() {
		Text sourceCodeHeader = generateCodeTitle(PSEUDOCODE_TITLE);
		SourceCodeProperties scProps = new SourceCodeProperties();
		scProps.set(AnimationPropertiesKeys.CONTEXTCOLOR_PROPERTY, Color.BLUE);
		scProps.set(AnimationPropertiesKeys.FONT_PROPERTY, new Font(
				"Monospaced", Font.PLAIN, 12));
		scProps.set(AnimationPropertiesKeys.HIGHLIGHTCOLOR_PROPERTY, Color.RED);
		scProps.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		code = lang.newSourceCode(new Offset(0, -10, sourceCodeHeader,
				AnimalScript.DIRECTION_SW), "PseudoCode", null, scProps);
		code.addCodeLine("xeulid(int a, int b, int gcd, int x, int y)", null,
				0, null);
		code.addCodeLine("begin", null, 0, null);
		code.addCodeLine("int q, r, xtmp, ytmp, sign", null, 1, null);
		code.addCodeLine("int x0,x1,y0,y1", null, 1, null);
		code.addCodeLine("x0 = 1 x1 = 0", null, 1, null);
		code.addCodeLine("y0 = 0 y1 = 1", null, 1, null);
		code.addCodeLine("sign = 1", null, 1, null); //zeile 6
		code.addCodeLine("while (b != 0)", null, 1, null);
		code.addCodeLine("r = a%b", null, 2, null);
		code.addCodeLine("q = a/b", null, 2, null);
		code.addCodeLine("a = b", null, 2, null);
		code.addCodeLine("b = r", null, 2, null);
		code.addCodeLine("xtmp = x1", null, 2, null); //zeile 12
		code.addCodeLine("ytmp = y1", null, 2, null); // zeile 13
		code.addCodeLine("x1 = q*x1 + x0", null, 2, null);
		code.addCodeLine("y1 = q*y1 + y0", null, 2, null);
		code.addCodeLine("x0 = xtmp", null, 2, null);
		code.addCodeLine("y0 = ytmp", null, 2, null);
		code.addCodeLine("sign = -sign", null, 2, null); //zeile 18
		code.addCodeLine("end while", null, 1, null);
		code.addCodeLine("x = sign*x0", null, 1, null);
		code.addCodeLine("y = -sign*y0", null, 1, null);
		code.addCodeLine("gcd = a", null, 1, null);
		code.addCodeLine("end", null, 0, null);
	}

	/**
	 * Erzeugt Titel f�r SourceCode
	 */
	private Text generateCodeTitle(String title) {
		TextProperties textProperties = new TextProperties();
		textProperties.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		textProperties.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		return lang.newText(new Offset(0, 20, headerBackground,
				AnimalScript.DIRECTION_SW), title, "sourcecodeheader", null,
				textProperties);
	}

	/**
	 * Erzeugt Tabelle fuer erweitertenEuklid Form: | q | r | x | y | a | b | x0
	 * | x1 | y0 | y1 |
	 */
	private void algorithmus() {
		String[][] matrix = toStringMatrix();
		String[][] stepMatrix = new String[matrix.length][matrix[0].length];
		MatrixProperties prob = new MatrixProperties();
		prob.set(AnimationPropertiesKeys.COLOR_PROPERTY, Color.BLACK);
		prob.set(AnimationPropertiesKeys.FILLED_PROPERTY, true);
		prob.set(AnimationPropertiesKeys.ELEMENTCOLOR_PROPERTY, Color.BLACK);
		prob.set(AnimationPropertiesKeys.FILL_PROPERTY, Color.WHITE);
		prob.set(AnimationPropertiesKeys.DEPTH_PROPERTY, 1);
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				stepMatrix[i][j] = "";
			}
		}
		for (int i = 0; i < matrix.length; i++) {
			stepMatrix[i][0] = matrix[i][0];
		}
		for (int i = 0; i < matrix[0].length; i++) {
			stepMatrix[0][i] = matrix[0][i];
		}
		table = lang.newStringMatrix(new Offset(200, 230, code,
				AnimalScript.DIRECTION_NE), stepMatrix, "Matrix", null, prob);
		lang.nextStep();

		code.highlight(0);
		table.put(1, 1, matrix[1][1], null, defaultTiming);
		table.put(1, 2, matrix[1][2], null, defaultTiming);
		table.highlightCell(1, 1, null, defaultTiming);
		textA.setText(A_INIT + matrix[1][1], null, null);
		textA.show();
		table.highlightCell(1, 2, null, defaultTiming);
		textB.setText(B_INIT + matrix[1][2], null, null);
		textB.show();
		lang.nextStep();

		table.unhighlightCell(1, 1, null, defaultTiming);
		table.unhighlightCell(1, 2, null, defaultTiming);
		code.toggleHighlight(0, 2);
		textQ.show();
		textR.show();
		lang.nextStep();

		code.toggleHighlight(2, 4);
		code.highlight(5);
		table.put(3, 1, matrix[3][1], null, defaultTiming);
		table.put(3, 2, matrix[3][2], null, defaultTiming);
		table.put(4, 1, matrix[4][1], null, defaultTiming);
		table.put(4, 2, matrix[4][2], null, defaultTiming);
		table.highlightCell(3, 1, null, defaultTiming);
		table.highlightCell(3, 2, null, defaultTiming);
		table.highlightCell(4, 1, null, defaultTiming);
		table.highlightCell(4, 2, null, defaultTiming);
		lang.nextStep();

		table.unhighlightCell(3, 1, null, defaultTiming);
		table.unhighlightCell(3, 2, null, defaultTiming);
		table.unhighlightCell(4, 1, null, defaultTiming);
		table.unhighlightCell(4, 2, null, defaultTiming);
		code.unhighlight(4);
		code.toggleHighlight(5, 6);
		textSIGN.setText(SIGN1 + sign, null, defaultTiming);
		textSIGN.show();
		lang.nextStep();

		code.toggleHighlight(6, 7);
		lang.nextStep();

		code.unhighlight(7);
		for (int i = 2; i < matrix[0].length - 1; i++) {
			code.highlight(8);
			textR.setText(R + matrix[1][i + 1], null, defaultTiming);
			table.put(1, i + 1, matrix[1][i + 1], null, defaultTiming);
			table.highlightCell(1, i + 1, null, defaultTiming);
			lang.nextStep();

			code.toggleHighlight(8, 9);
			table.unhighlightCell(1, i + 1, null, defaultTiming);
			textQ.setText(Q + matrix[2][i], null, defaultTiming);
			table.put(2, i, matrix[2][i], null, defaultTiming);
			table.highlightCell(2, i, null, defaultTiming);
			lang.nextStep();

			table.unhighlightCell(2, i, null, defaultTiming);
			textA.setText(A + matrix[1][i], null, defaultTiming);
			table.highlightCell(1, i, null, defaultTiming);
			code.toggleHighlight(9, 10);
			lang.nextStep();

			table.unhighlightCell(1, i, null, defaultTiming);
			table.highlightCell(1, i + 1, null, defaultTiming);
			textB.setText(B + matrix[1][i + 1], null, defaultTiming);
			code.toggleHighlight(10, 11);
			lang.nextStep();

			table.unhighlightCell(1, i + 1, null, defaultTiming);
			code.toggleHighlight(11, 12);
//			textXTMP.setText(XTMP + xs[1], defaultTiming, defaultTiming);
//			textXTMP.show();
			lang.nextStep();

			code.toggleHighlight(12, 13);
//			textYTMP.setText(YTMP + ys[1], null, defaultTiming);
//			textYTMP.show();
			lang.nextStep();

			table.highlightCell(3, i + 1, null, defaultTiming);
			table.put(3, i + 1, matrix[3][i + 1], null, defaultTiming);
			code.toggleHighlight(13, 14);
			lang.nextStep();

			table.unhighlightCell(3, i + 1, null, defaultTiming);
			table.highlightCell(4, i + 1, null, defaultTiming);
			table.put(4, i + 1, matrix[4][i + 1], null, defaultTiming);
			code.toggleHighlight(14, 15);
			lang.nextStep();

			table.unhighlightCell(4, i + 1, null, defaultTiming);
			code.toggleHighlight(15, 16);
			lang.nextStep();

			code.toggleHighlight(16, 17);
			lang.nextStep();

			code.toggleHighlight(17, 18);
			textSIGN.setText(SIGN2 + -sign, null, defaultTiming);
			textSIGN.show();
			lang.nextStep();

			code.unhighlight(18);
		}

		code.highlight(19);
		lang.nextStep();

		textX.setText(X + x, null, defaultTiming);
		textX.show();
		code.toggleHighlight(19, 20);
		lang.nextStep();

		textY.setText(Y + y, null, defaultTiming);
		textY.show();
		code.toggleHighlight(20, 21);
		lang.nextStep();

		textGCD.setText(GCD + gcd, null, defaultTiming);
		textGCD.show();
		code.toggleHighlight(21, 22);
		lang.nextStep();
		
		code.unhighlight(22);
		code.highlight(23);
		lang.nextStep();
		
		code.unhighlight(23);
		
		Text t = lang.newText(new Offset(0, 20, code,AnimalScript.DIRECTION_SW), "Ergebnis: x = " + x + ", y = " + y + ", gcd = "+ gcd, "", null);
	}

	/*
	 * Felder fuer erweiterterEuklid
	 */
	private int gcd;
	private int x;
	private int y;
//	private int xx;
	int[] xs;
	int[] ys;
//	private int yy;
	private int sign = 1;

	/*
	 * Methoden fuer erweiterterEuklid
	 */

	/**
	 * <h3>Erweiterter Euklid</h3>
	 * <p>
	 * x*a+y*b = gcd <\p>
	 * 
	 * @param a
	 * @param b
	 * @param gcd
	 * @param x
	 * @param y
	 */
	public void erweiterterEuklid(int a, int b) {
		rk.add(a + "");
		rk.add(b + "");
		qk.add("");
		int q, r, xx, yy, sign;
		xs = new int[2];
		ys = new int[2];
		xs[0] = 1;
		xk.add(xs[0] + "");
		xs[1] = 0;
		xk.add(xs[1] + "");
		ys[0] = 0;
		yk.add(ys[0] + "");
		ys[1] = 1;
		yk.add(ys[1] + "");
		sign = 1;
		// System.out.println("a = " + a + " b = " + b);
		while (b != 0) {
			r = a % b;
			rk.add(r + "");
			q = a / b;
			qk.add(q + "");
			a = b;
			b = r;
			xx = xs[1];
			yy = ys[1];
			xs[1] = q * xs[1] + xs[0];
			xk.add(xs[1] + "");
			ys[1] = q * ys[1] + ys[0];
			yk.add(ys[1] + "");
			xs[0] = xx;
			ys[0] = yy;
			sign = -sign;
			// System.out.println("a = " + a + " b = " + b + " r " + r + " q " +
			// q);
		}
		x = sign * xs[0];
		y = -sign * ys[0];
		gcd = a;
	}

	public String[][] toStringMatrix() {
		String[][] matrix = new String[5][rk.size() + 1];
		matrix[0][0] = "k";
		matrix[1][0] = "rk";
		matrix[2][0] = "qk";
		matrix[3][0] = "xk";
		matrix[4][0] = "yk";
		for (int i = 0; i < rk.size(); i++) {
			matrix[0][i + 1] = i + "";
			matrix[1][i + 1] = rk.get(i);
			if (i >= qk.size()) {
				matrix[2][i + 1] = "";
			} else {
				matrix[2][i + 1] = qk.get(i);
			}
			matrix[3][i + 1] = xk.get(i);
			matrix[4][i + 1] = yk.get(i);

		}
		return matrix;
	}

	public static void main(String[] args) {
		ErweiterterEuklid erweiterterEuklid = new ErweiterterEuklid();
		erweiterterEuklid.generateForMain();
		erweiterterEuklid.init();
		 erweiterterEuklid.erweiterterEuklid(10, 4);
		 erweiterterEuklid.algorithmus();
//		 erweiterterEuklid.initForMain();

//		 System.out.println(erweiterterEuklid.getCodeExample());
		SimpleFileWriter.writeFile(new File(
				"src/generatorImplementations/misc/ErweiterterEuklid.asu"),
				erweiterterEuklid.lang.toString());
		//		
		System.out.println(erweiterterEuklid.lang.toString());

	}

	@Override
	public String generate(AnimationPropertiesContainer arg0,
			Hashtable<String, Object> arg1) {
		generatePseudoCode();
		generateText();
		defaultTiming = new TicksTiming(100);
		int a = (Integer) arg1.get("a - Dividend");
		int b = (Integer) arg1.get("b - Divisor");
		init();
		erweiterterEuklid(a, b);
		algorithmus();
		return lang.toString();
	}

	public void generateForMain() {
		generatePseudoCode();
		generateText();
		

	}

	@Override
	public String getAlgorithmName() {
		return HEADER_TITLE;
	}

	@Override
	public String getAnimationAuthor() {
		return AUTHOR;
	}

	@Override
	public String getCodeExample() {
		return pseudoCodeBuchmann;
	}

	@Override
	public Locale getContentLocale() {
		return Locale.GERMANY;
	}

	@Override
	public String getDescription() {
		return text;
	}

	@Override
	public String getFileExtension() {
		return ".asu";
	}

	@Override
	public GeneratorType getGeneratorType() {
		return new GeneratorType(GeneratorType.GENERATOR_TYPE_MORE);
	}

	@Override
	public String getName() {
		return HEADER_TITLE + " " + P_CODE_TITLE;
	}

	@Override
	public String getOutputLanguage() {
		return Generator.PSEUDO_CODE_OUTPUT;
	}
}